#!/usr/bin/env node

import mysql from 'mysql2/promise';
import { dateFormat, titleCase, toCamelCase, toPascalCase, upperCaseToCamelCase } from './util.mjs';
import fs from 'fs/promises';
import conf from './conf.mjs';
import path from 'path';
import { fileURLToPath } from 'url';
import { Command } from 'commander';
const program = new Command();
program
  .option('-t, --table <type>', '表名，多个可以用(,)隔开')
  .option('-e, --entity <type>', '实体类名，默认为表名的大驼峰，多个可以用(,)隔开')
  .option('-p, --package <type>', '包名，默认为SpringBoot启动文件的包名')
  .option('-a, --author <type>', '作者')
  .option('-f, --focus', '强制复写文件')
  .option(
    '-o, --output <type>',
    '输出文件夹，如不是SpringBoot项目默认为./out目录，是的话会分布到标准的SpringBoot目录中'
  );

program.parse(process.argv);

main(program.opts());
async function main(options) {
  if (!options.table) {
    throw new Error('请输入表名');
  }

  const [appPath, pack] = await getPackage();

  conf.package = options.package || pack || conf.package;
  conf.author = options.author || conf.author;
  conf.output = options.output || conf.output;
  conf.appPath = appPath || conf.appPath;
  conf.focus = options.focus;

  if (!conf.package) {
    throw new Error('没有找到包名');
  }

  const database = conf.db.database;

  const tableNames = options.table.split(' ');
  const entityNames = options.entity ? options.entity.split(' ') : tableNames.map((t) => toPascalCase(t));

  const connection = await mysql.createConnection({ ...conf.db });

  try {
    await Promise.all(
      tableNames.map((t, i) => generateTable(connection, { tableName: t, entityName: entityNames[i], database }))
    );
  } catch (e) {
    console.error(e);
  } finally {
    connection.end();
  }
}

async function generateTable(connection, { tableName, entityName, database }) {
  let [fields] = await connection.query(
    `SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE  TABLE_SCHEMA = ? AND table_name = ? ORDER BY ordinal_position `,
    [database, tableName]
  );
  const [[{ tableComment }]] = await connection.query(
    `SELECT TABLE_NAME tableName, TABLE_COMMENT tableComment FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`,
    [database, tableName]
  );

  fields = fields.map((t) => {
    const obj = {};
    Object.keys(t).forEach((key) => {
      obj[upperCaseToCamelCase(key)] = t[key];
    });
    const imports = conf.dateTypeConf[t.dataType]?.imports;

    if (imports) {
      imports.forEach((imp) => {
        if (!conf.defaultImport.includes(imp)) {
          conf.defaultImport.push(imp);
        }
      });
    }
    return { ...obj, columnNameCase: toCamelCase(obj.columnName) };
  });

  const params = {
    imports: conf.defaultImport.map((t) => `${t};`),
    fields,
    tableName,
    entityName,
    tableComment,
    date: dateFormat(new Date()),
    packageName: conf.package,
    author: conf.author,
    initField({ columnKey, extra, columnComment, dataType, columnName, columnNameCase }) {
      let tableField = `@TableField("${columnName}")`;
      const JsonFormat = conf.dateTypeConf[dataType]?.JsonFormat;
      if (columnKey === 'PRI') {
        tableField = `@TableId(value = "${columnName}", type = IdType.AUTO)`;
      }
      return `@ApiModelProperty(value = "${columnComment}")
    ${tableField}${
      JsonFormat
        ? `
    @JsonFormat(pattern="${JsonFormat}")`
        : ''
    }
    private ${conf.dateTypeConf[dataType]?.type || 'String'} ${columnNameCase};`;
    },
  };

  const dirPath = conf.appPath;

  await generate('entity', params);
  await generate('mapper', params);
  await generate('service', params);
  await generate('serviceImpl', params);

  async function generate(fileName, params) {
    const __filename = fileURLToPath(import.meta.url);

    const entityTpl = await fs.readFile(path.join(__filename, '..', `./template/${fileName}`), 'utf8');
    const entity = new Function(...Object.keys(params), `return \`${entityTpl}\``)(...Object.values(params));
    let filePath = path.join(dirPath, params.entityName + titleCase(fileName) + '.java');

    switch (fileName) {
      case 'entity':
        filePath = path.join(dirPath, 'entity', params.entityName + '.java');
        break;
      case 'mapper':
        filePath = path.join(dirPath, 'mapper', params.entityName + 'Mapper.java');
        break;
      case 'service':
        filePath = path.join(dirPath, 'service', params.entityName + 'Service.java');
        break;
      case 'serviceImpl':
        filePath = path.join(dirPath, 'service', 'impl', params.entityName + 'ServiceImpl.java');
        break;
    }

    try {
      await fs.stat(path.resolve(filePath, '..'));
    } catch (e) {
      await fs.mkdir(path.resolve(filePath, '..'));
    }
    try {
      const stat = await fs.stat(filePath);
      if (stat.isFile()) {
        if (conf.focus) {
          await fs.writeFile(filePath, entity);
          console.info(`生成文件：${filePath}`);
        } else {
          console.error(`文件已存在，跳过生成：${filePath}`);
        }
      }
    } catch (e) {
      await fs.writeFile(filePath, entity);
      console.info(`生成文件：${filePath}`);
    }
  }
}

async function getPackage(dir = path.join(process.cwd())) {
  const files = await fs.readdir(dir);
  for (const file of files) {
    let fPath = path.join(dir, file);
    const stat = await fs.stat(fPath);

    if (stat.isFile() && file.endsWith('.java')) {
      const content = await fs.readFile(fPath, 'utf8');
      const lines = content.split('\n').map((t) => t.trim());
      if (lines.find((t) => t.startsWith('@SpringBootApplication'))) {
        let packageName = lines
          .find((t) => t.startsWith(`package `))
          .split(' ')
          .filter(Boolean)[1]
          .replace(';', '');
        if (packageName) {
          return [path.join(fPath, '..'), packageName];
        }
      }
    }
    if (stat.isDirectory()) {
      let fp = await getPackage(fPath);
      if (fp) {
        return fp;
      }
    }
  }
}
